// Copyright 2013 Google Inc. All Rights Reserved.
// Add futex interface for Bionic.

#include <irt_syscalls.h>

#include <bionic_futex.h>
#include <errno.h>
#include <nacl_timespec.h>
#include <nacl_timeval.h>
#include <stdlib.h>
#include <unistd.h>

/* TODO(noelallen)
 * Need to check IRT syscalls values */

int  __futex_syscall4(volatile void *ftx, int op, int val,
                      const struct timespec *timeout) {
  /* FUTEX_FD, FUTEX_REQUEUE, and FUTEX_CMP_REQUEUE are not used
   * by android.
   * TODO(crbug.com/243244): Support these operations. In theory, NDK
   * apps can call this for the operations we do not support. */


  switch (op) {
    case FUTEX_WAIT:
    case FUTEX_WAIT_PRIVATE: {
      // We need to convert the ABI of timespec from bionic's to NaCl's.
      struct nacl_abi_timespec nacl_timeout;
      struct nacl_abi_timespec *nacl_timeout_ptr = NULL;
      if (timeout) {
        // Functions from nacl-glibc expects absolute time for this.
        struct nacl_abi_timeval tv;
        int r = __nacl_irt_gettod((struct timeval*) &tv);
        if (r != 0) {
          // Maybe this should not happen.
          errno = EFAULT;
          return -1;
        }

        static const int kNanosecondsPerSecond = 1000000000;
        // NaClCommonSysCond_Timed_Wait_Abs does not validate timeout
        // and it has a TODO instead. So we should check the value.
        if (timeout->tv_nsec >= kNanosecondsPerSecond) {
          errno = EINVAL;
          return -1;
        }
        long sec = timeout->tv_sec + tv.tv_sec;
        long nsec = timeout->tv_nsec + tv.tv_usec * 1000;
        sec += nsec / kNanosecondsPerSecond;
        if (sec < 0 || nsec < 0) {
          errno = EINVAL;
          return -1;
        }
        nsec %= kNanosecondsPerSecond;
        nacl_timeout.tv_sec = sec;
        nacl_timeout.tv_nsec = nsec;
        nacl_timeout_ptr = &nacl_timeout;
      }
      return __nacl_irt_futex_wait_abs(ftx, val,
                (const struct timespec*) nacl_timeout_ptr);
    }
    case FUTEX_WAKE:
    case FUTEX_WAKE_PRIVATE: {
      int count;
      return __nacl_irt_futex_wake(ftx, val, &count);
    }
    default: {
      static const int kStderrFd = 2;
      static const char kMsg[] = "futex syscall called with unexpected op!";
      write(kStderrFd, kMsg, sizeof(kMsg) - 1);
      abort();
    }
  }
}

int __futex_syscall3(volatile void *ftx, int op, int val) {
  return __futex_syscall4(ftx, op, val, NULL);
}
